﻿#region Copyright
// Copyright (c) 2011-2012 RaisingForce Team (Alikin Sergey)
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
#endregion

using System.Diagnostics.Contracts;
using System.Threading;
using RaisingForce.Raise.Hardware.Rules;
using RaisingForce.Raise.Measures;

namespace RaisingForce.Raise.Hardware.Emulation
{
    sealed internal class EmulationEngine :
        RobotEngine
    {
        #region Constructors

        public EmulationEngine(
            object hardwareSyncObject,
            Distance wheelRunDistance,
            Time wheelRunTime,
            Time leftTurnTime,
            Time rightTurnTime)
            : base(hardwareSyncObject)
        {
            Contract.Requires(hardwareSyncObject != null);
            Contract.Requires(RobotHardwareOptionsRules.DistanceIsValid(wheelRunDistance));
            Contract.Requires(RobotHardwareOptionsRules.TimeIsValid(wheelRunTime));
            Contract.Requires(RobotHardwareOptionsRules.TimeIsValid(leftTurnTime));
            Contract.Requires(RobotHardwareOptionsRules.TimeIsValid(rightTurnTime));
            this.wheelRunDistance = wheelRunDistance;
            this.wheelRunTime = wheelRunTime;
            this.leftTurnTime = leftTurnTime;
            this.rightTurnTime = rightTurnTime;
        }

        #endregion

        #region Private Declarations

        private readonly Distance wheelRunDistance;

        private readonly Time wheelRunTime;

        private readonly Time leftTurnTime;

        private readonly Time rightTurnTime;

        private EventWaitHandle waitHandle; // = null

        private readonly object waitSyncObject = new object();

        private void waitForTimeout(Time time)
        {
            try
            {
                time.Wait(out this.waitHandle);
            }
            finally
            {
                if (this.waitHandle != null)
                {
                    lock (this.waitSyncObject)
                    {
                        if (this.waitHandle != null)
                        {
                            this.waitHandle.Dispose();
                            this.waitHandle = null;
                        }
                    }
                }
            }
        }

        private void stopWaiting()
        {
            if (this.waitHandle != null)
            {
                lock (this.waitSyncObject)
                {
                    if (this.waitHandle != null)
                    {
                        this.waitHandle.Set();
                    }
                }
            }
        }

        #endregion

        #region Base Overrides

        protected override void RotateHardware(
            Angle rotationAngle,
            RotationDirection rotationDirection)
        {
            Time turnTime;
            switch (rotationDirection)
            {
                case RotationDirection.Clockwise:
                    turnTime = this.rightTurnTime;
                    break;
                case RotationDirection.CounterClockwise:
                default:
                    turnTime = this.leftTurnTime;
                    break;
            }
            double angleInDegree = rotationAngle
                .GetNormalized()
                .Get(AngleMeasures.Degree);
            double multiplier = angleInDegree / 360;
            Time time = turnTime * multiplier;
            this.waitForTimeout(time);
        }

        protected override void MoveHardware(
            Distance movementDistance,
            MovementDirection movementDirection)
        {   // предполагается, что время эмуляции движения не зависит от его направления.
            double multiplier =
                movementDistance.Get(DistanceMeasures.Centimetre)
                / this.wheelRunDistance.Get(DistanceMeasures.Centimetre);
            Time time = this.wheelRunTime * multiplier;
            this.waitForTimeout(time);
        }

        protected override void StopHardware()
        {
            this.stopWaiting();
        }

        #endregion
    }
}
